-- ======================================================================
--  Hell Watch Lua IO App
-- 
-- Copyright 2014 Hell-Prototypes
--
-- http://www.hellprototypes.com
--
-- This is free software, licensed under the terms of the GNU General
-- Public License as published by the Free Software Foundation.
-- ======================================================================
spi = require "mod_fw_spi"
iHEX = require "mod_iHEX"
printf = require "mod_print"

-- Define ----------------------------------------------------------------------
FLASH_PAGE_SIZE = 32
FLASH_PAGE_NUM  = 64

-- SPI pin index define --------------------------------------------------------
SPI_MOSI_PIN = 3
SPI_MISO_PIN = 2
SPI_SCK_PIN  = 1
SPI_SS_PIN   = 0

function isp_init()
	if not spi.init(SPI_MOSI_PIN, SPI_MISO_PIN, SPI_SCK_PIN, SPI_SS_PIN) then
		print("<ISP> spi.io_init fail")
		return false
	end

	spi.pin_reset(SPI_SS_PIN)
	spi.pin_reset(SPI_SCK_PIN)
	spi.pin_reset(SPI_MOSI_PIN)
	spi.pin_set(SPI_SS_PIN)
	spi.pin_reset(SPI_SS_PIN)
	spi.ms_delay(25)

	return true
end
-- -----------------------------------------------------------------------------
function CMD_Poll_Busy()
	local busy
	local wait = 20
	
	while wait > 0 do
		wait = wait - 1
		busy = spi.send_32bit(0xf0000000)
		--print(string.format("<spi> busy = 0x%x", busy))
		if not bit32.btest(busy, 1) then
			return true
		end
		spi.ms_delay(1)
		--io.write(".")
	end
	
	return false
end
-- -----------------------------------------------------------------------------
function CMD_Programming_Enable()
	spi.send_32bit(0xAC530000)
end
-- -----------------------------------------------------------------------------
function CMD_Read_Fuse_Bytes()
	local low_byte, high_byte

	low_byte  = spi.send_32bit(0x50000000)
	high_byte = spi.send_32bit(0x58080000)
	
	return high_byte*256 + low_byte
end
-- -----------------------------------------------------------------------------
function CMD_Read_Signature_Byte()
	local sb0, sb1, sb2

	sb0 = spi.send_32bit(0x30000000)
	sb1 = spi.send_32bit(0x30000100)
	sb2 = spi.send_32bit(0x30000200)

	return 	sb2*0x10000 + sb1*0x100 + sb0
end
-- -----------------------------------------------------------------------------
function CMD_Chip_Erase()
	spi.send_32bit(0xAC800000)
	return CMD_Poll_Busy()
end
-- -----------------------------------------------------------------------------
function CMD_Load_One_Word(data, word_addr)
	local cmd0 = 0x40000000 + bit32.lshift(word_addr, 8) + bit32.band(data, 0xFF)
	local cmd1 = 0x48000000 + bit32.lshift(word_addr, 8) + bit32.rshift(bit32.band(data, 0xFF00), 8)
	--print(string.format("<ISP> cmd0 = 0x%x, cmd1 = 0x%x", cmd0, cmd1))
	spi.send_32bit(cmd0)
	spi.send_32bit(cmd1)
end
-- -----------------------------------------------------------------------------
function CMD_Load_One_Page(t_page_data, page_num)
	if #t_page_data < 2 then
		return false
	end

	local word_data = 0
	local data_index = 1
	--print("============= " .. page_num .. " =============")
	for word_addr = 0, #t_page_data/2 - 1 do
		word_data = bit32.band(t_page_data[data_index], 0xFF) + bit32.band(t_page_data[data_index + 1], 0xFF)*0x100
		data_index = data_index + 2
		CMD_Load_One_Word(word_data, word_addr)
	end
	
	return true
end
-- -----------------------------------------------------------------------------
function CMD_Write_One_Page(t_page_data, page_num)
	if page_num >= FLASH_PAGE_NUM then
		return false
	end
	if not CMD_Load_One_Page(t_page_data, page_num) then
		return false
	end

	local cmd = 0x4C000000 + bit32.lshift(page_num, 12)
	--print(string.format("<ISP> write page cmd = 0x%x", cmd))
	spi.send_32bit(cmd)

	return CMD_Poll_Busy()
end
-- -----------------------------------------------------------------------------
function CMD_Program_Flash(start_address, t_program_data)
	local page_num = math.floor(start_address/FLASH_PAGE_SIZE)
	local t_page_data={}
	local t_page_data_index = 1

	io.write("<ISP> " .. math.floor(#t_program_data/FLASH_PAGE_SIZE) .. " Page to update: \r\n")
	for i=1, #t_program_data do
		t_page_data[t_page_data_index] = t_program_data[i]
		t_page_data_index = t_page_data_index + 1
		if (t_page_data_index > FLASH_PAGE_SIZE) or ( i == #t_program_data) then
			local percent = math.floor((1000*i)/#t_program_data)
			io.write(string.format("\r\tUpdate page: %d,\tProgress: %%%0.1f\t", page_num, percent/10))
			-- print("===============Write page: " .. page_num .. " ===============")
			if not CMD_Write_One_Page(t_page_data, page_num) then
				print("")
				return false
			end
			page_num = page_num + 1
			t_page_data = {}
			t_page_data_index = 1
		end
	end

	print("")
	return true
end
-- -----------------------------------------------------------------------------
function CMD_Program_EEPROM(start_address, t_program_data)
    local cmd
    local addr

	io.write("<ISP> " .. #t_program_data .. " Byte will be write to EEPROM: ")
	for i=1, #t_program_data do
        addr = (start_address + i - 1) * 0x100
        cmd = 0xC0000000 + addr + t_program_data[i]
    	--print(string.format("<ISP> write eeprom cmd = 0x%x", cmd))
    	spi.send_32bit(cmd)
    	if not CMD_Poll_Busy() then
    		return false
    	end
	end

	print("")
	return true
end
-- -----------------------------------------------------------------------------
function CMD_Read_Program(start_address, data_len)
	local word_len = math.floor(data_len/2)
end
-- -----------------------------------------------------------------------------
function ISP_Program_Flash(pgm_hex_file)
	local pgm_raw_data = {}
	local pgm_start_addr = 0
	
	local f=io.open(pgm_hex_file,"r")
   	if f~=nil then
		io.close(f)
	else
		print("<ISP> Error: Program file not exist, file name: " .. pgm_hex_file)
		return
	end
	
	pgm_start_addr, pgm_raw_data = iHEX.Read_File(pgm_hex_file)

	if CMD_Chip_Erase() then
		print("<ISP> Chip Erase Done ...")
		if CMD_Program_Flash(pgm_start_addr, pgm_raw_data) then
			print("<ISP> Program Flash DONE ...")
		else
			print("<ISP> Error: CMD_Program_Flash fail")
		end
		
	else
		print("<ISP> Error: CMD_Chip_Erase fail")
	end
end
-- -----------------------------------------------------------------------------
function ISP_Program_EEPROM(epm_hex_file)
	local epm_raw_data = {}
	local epm_start_addr = 0
	
	local f=io.open(epm_hex_file,"r")
   	if f ~= nil then
		io.close(f)
	else
		print("<ISP> Error: EEPROM file not exist, file name: " .. epm_hex_file)
		return
	end
	
	epm_start_addr, epm_raw_data = iHEX.Read_File(epm_hex_file)

	if type(epm_raw_data) == "table" then
		print("========== EEPROM data - Hex Format ==========")
		for i=1, #epm_raw_data do
			io.write(string.format("%02x ", epm_raw_data[i]):upper())
			if i%16 == 0 then
				print("")
			end
		end
		print("\r\n===============================================\r\n")
		
		print(string.format("start address = 0x%04x", epm_start_addr))
	else
		print("<ISP> Error: fail to read hex file.")
		return
	end
	
	if CMD_Program_EEPROM(epm_start_addr, epm_raw_data) then
		print("<ISP> Program EEPROM DONE ...")
	else
		print("<ISP> Error: CMD_Program_EEPROM fail")
	end
end
function ISP_Read_EEPROM(len)
    local cmd
    local addr
    local eeprom = {}

	print("<ISP> Read EEPROM: ")
	for i=1, len do
        addr = (i - 1) * 0x100
        cmd = 0xA0000000 + addr
    	eeprom[i] = spi.send_32bit(cmd)
		io.write(string.format("%04x,", i-1):upper())
		if i%16 == 0 then
			print("")
		end
    	--print(string.format("<ISP> Read eeprom[%d] = 0x%x", i, eeprom))
    	if not CMD_Poll_Busy() then
    		return false
    	end
	end
	
	printf.table(eeprom)

	print("")
	return true
end
-- -----------------------------------------------------------------------------
function CMD_Write_Fuse(wr_low_byte)
	local fuse
    local fuse_byte
    
    io.write("> Input fuse byte data to write(hex format): 0x")
	io.flush()
	fuse = io.read()
    fuse_byte = tonumber(fuse, 16)
    if (fuse_byte ~= nil) and (fuse_byte >= 0) and (fuse_byte < 0x100) then
        if wr_low_byte then
            spi.send_32bit(0xACA00000 + fuse_byte)
        else
            spi.send_32bit(0xACA80000 + fuse_byte)
        end
    else
        return false
    end

	return true
end
-- -----------------------------------------------------------------------------
function main(arg_flash_file_name, arg_eeprom_file_name)
	if not spi.device_open() then
		print("<ISP> spi.device_open fail")
		return
	end

	if not isp_init() then
		print("<ISP> isp_init fail")
	end

	CMD_Programming_Enable()
	
	if arg_flash_file_name ~= nil then
		ISP_Program_Flash(arg_flash_file_name)
        if arg_eeprom_file_name ~= nil then
            ISP_Program_EEPROM(arg_eeprom_file_name)
        end
	else
		while true do
			print("")
			print("======= Tiny ISP Menu ======")
			print("= 1. Read Fuse Bytes       =")
			print("= 2. Read Signature_Byte   =")
			print("= 3. Write Fuse Low Byte   = need test")
			print("= 4. Write Fuse High Byte  = need test")
			print("= 5. Program Flash         =")
			print("= 6. Program EEPROM        = need test")
			print("= 7. Read Flash            =")
			print("= 8. Read EEPROM           =")
			print("= 9. Chip Erase            =")
			print("= Q. Exit                  =")
			print("============================")
			io.write("> Select Operation: ")
			io.flush()
			local input = io.read()
			print("")
			if input == "1" then
				fuse = CMD_Read_Fuse_Bytes()
				print(string.format("<ISP> fuse = 0x%x", fuse))
			elseif input == "2" then
				signature = CMD_Read_Signature_Byte()
				print(string.format("<ISP> signature = 0x%x", signature))
			elseif input == "3" then
                if CMD_Write_Fuse(true) then
                    print("Write Fuse Low Byte SUCC")
                else
				    print("Write Fuse Low Byte FAIL")
                end
			elseif input == "4" then
                if CMD_Write_Fuse(false) then
                    print("Write Fuse High Byte SUCC")
                else
				    print("Write Fuse High Byte FAIL")
                end
			elseif input == "5" then
				local file_name
				io.write("> Input program file name: ")
				io.flush()
				file_name = io.read()

				if file_name ~= nil then
					ISP_Program_Flash(file_name)
				end
			elseif input == "6" then
                local file_name
				io.write("> Input eeprom file name: ")
				io.flush()
				file_name = io.read()

				if file_name ~= nil then
					ISP_Program_EEPROM(file_name)
				end
			elseif input == "7" then
				print("Todo ...")
			elseif input == "8" then
				ISP_Read_EEPROM(64)
			elseif input == "9" then
				if CMD_Chip_Erase() then
					print("<ISP> Chip Erase done")
				else
					print("<ISP> Error: CMD_Chip_Erase fail")
				end
			elseif input == "Q" or input == "q" then
				break
			else
				print("> Unsupport Key: " .. input)
			end
			
			io.write("\r\nAny key to continue ...")
			io.flush()
			io.read()
			print("")
		end
	end

	spi.free()
	if not spi.device_close() then
		print("<ISP> spi.device_close fail")
	end
end

main(arg[1],arg[2])
